#include <memory.h>
#include <malloc.h>
#include <errno.h>
#include <util.h>
#include <string.h>

struct memory_allocator *default_memory_allocator;

static LIST_HEAD(memory_allocators);

void *kalloc(struct memory_zone *zone, int size)
{
	unsigned int tmp;
	
	tmp = zone->alloc_end + size;
	if ((tmp >= zone->start_addr) && (tmp <= zone->start_addr + zone->length)) {
		tmp = zone->alloc_end;
		zone->alloc_end = tmp;
		return (void *)tmp;
	}
	return (void *)-1;
}

int memory_allocator_add(struct memory_allocator *allocator)
{
    if (IS_ERR(allocator) || (allocator == NULL))
        return -EINVAL;  

    list_add_tail(&allocator->n_allocator_list, &memory_allocators);

    if (default_memory_allocator == NULL) 
        default_memory_allocator = allocator;   

    return 0;
}

struct memory_allocator *memory_allocator_get(char *name)
{
    struct memory_allocator *ma; 
    struct list_head *p; 

	list_for_each(p, &memory_allocators) {
		ma = container_of(p, struct memory_allocator, n_allocator_list);
		if (strcmp(ma->name, name) == 0) 
			return ma;
	}
	
	return ERR_PTR(-ENODEV);
}

static void sort_zone(struct memory_zone *z) 
{
	struct list_head *p, *p1;
	struct memory_zone *z1;
	unsigned int tmp;
	
	tmp = (unsigned int)z->allocator->footprint(z);
	if (tmp != z->footprint) {
		p = &z->n_zone_type_list;
		if (z->type == MZ_DMA) {				
			for (p1 = p->prev; p1 != &memory_dma_zones; p1 = p1->prev) {
				z1 = container_of(p1, struct memory_zone, n_zone_type_list);
				if (z->length - z->footprint > z1->length - z1->footprint) {
					if (p1 != p->prev) 
						list_move(p, p1);
					break;
				}
			}
			if (p1 == &memory_dma_zones) 
				list_move(p, &memory_dma_zones);
		} else if (z->bank->type == MT_SRAM) {
			for (p1 = p->prev; p1 != &memory_sram_zones; p1 = p1->prev) {
				z1 = container_of(p1, struct memory_zone, n_zone_type_list);
				if (z->length - z->footprint > z1->length - z1->footprint) {
					if (p1 != p->prev) 
						list_move(p, p1);
					break;
				}
			}
			if (p1 == &memory_sram_zones) 
				list_move(p, &memory_sram_zones);
		} else if (z->bank->type == MT_SDRAM) {
			for (p1 = p->prev; p1 != &memory_sdram_zones; p1 = p1->prev) {
				z1 = container_of(p1, struct memory_zone, n_zone_type_list);
				if (z->length - z->footprint > z1->length - z1->footprint) {
					if (p1 != p->prev) 
						list_move(p, p1);
					break;
				}
			}
			if (p1 == &memory_sdram_zones) 
				list_move(p, &memory_sdram_zones);				
		}
	}
}

void *kmalloc(size_t bytes, unsigned int flags)
{
	void *mem;
	struct list_head *p;
	struct memory_zone *z;
	
	if (maf_is_dma(flags)) {
		for_each_dma_memory_zone(p) {
			z = container_of(p, struct memory_zone, n_zone_type_list);
			if (z->allocator) {
				mem = z->allocator->malloc(z, bytes);
				if (mem) {
					sort_zone(z);
					return mem;			
				}			
			}
		}
	} else if (maf_is_sram(flags)) {
		for_each_sram_memory_zone(p) {
			z = container_of(p, struct memory_zone, n_zone_type_list);
			if (z->allocator) {
				mem = z->allocator->malloc(z, bytes);
				if (mem) {
					sort_zone(z);
					return mem;			
				}			
			}
		}
	}

	for_each_sdram_memory_zone(p) {
		z = container_of(p, struct memory_zone, n_zone_type_list);
		if (z->allocator) {
			mem = z->allocator->malloc(z, bytes);
			if (mem) {
				sort_zone(z);
				return mem;			
			}			
		}
	}

	return ERR_PTR(-ENOMEM);
}

void kfree(void* mem)
{
	struct list_head *p;
	struct memory_zone *z;
	
	for_each_memory_zone(p) {
		z = container_of(p, struct memory_zone, n_zone_list);
		if ((z->start_addr <= (unsigned int)mem) || (z->start_addr + z->length > (unsigned int)mem)) {
			z->allocator->free(z, mem);
			sort_zone(z);
			return;
		}
	}
}

void *kcalloc(size_t n_elements, size_t elem_size, unsigned int flags)
{
	void *mem;
	struct list_head *p;
	struct memory_zone *z;
	
	if (maf_is_dma(flags)) {
		for_each_dma_memory_zone(p) {
			z = container_of(p, struct memory_zone, n_zone_type_list);
			if (z->allocator) {
				mem = z->allocator->calloc(z, n_elements, elem_size);
				if (mem) {
					sort_zone(z);
					return mem;			
				}			
			}
		}
	} else if (maf_is_sram(flags)) {
		for_each_sram_memory_zone(p) {
			z = container_of(p, struct memory_zone, n_zone_type_list);
			if (z->allocator) {
				mem = z->allocator->calloc(z, n_elements, elem_size);
				if (mem) {
					sort_zone(z);
					return mem;			
				}			
			}
		}
	}

	for_each_sdram_memory_zone(p) {
		z = container_of(p, struct memory_zone, n_zone_type_list);
		if (z->allocator) {
			mem = z->allocator->calloc(z, n_elements, elem_size);
			if (mem) {
				sort_zone(z);
				return mem;			
			}			
		}
	}

	return ERR_PTR(-ENOMEM);
}

void *krealloc(void* mem, size_t newsize)
{
	struct list_head *p;
	struct memory_zone *z;
	void *retp;
	
	for_each_memory_zone(p) {
		z = container_of(p, struct memory_zone, n_zone_list);
		if ((z->start_addr <= (unsigned int)mem) || (z->start_addr + z->length > (unsigned int)mem)) {
			retp = z->allocator->realloc(z, mem, newsize);
			if (retp)
				sort_zone(z);
			return retp;
		}
	}
	return ERR_PTR(-EINVAL);
}

void *krealloc_in_place(void* mem, size_t newsize)
{
	struct list_head *p;
	struct memory_zone *z;
	void *retp;
	
	for_each_memory_zone(p) {
		z = container_of(p, struct memory_zone, n_zone_list);
		if ((z->start_addr <= (unsigned int)mem) || (z->start_addr + z->length > (unsigned int)mem)) {
			retp = z->allocator->realloc_in_place(z, mem, newsize);
			if (retp)
				sort_zone(z);
			return retp;
		}
	}
	return ERR_PTR(-EINVAL);
}

void *kmemalign(size_t alignment, size_t bytes, unsigned int flags)
{
	void *mem;
	struct list_head *p;
	struct memory_zone *z;
	
	if (maf_is_dma(flags)) {
		for_each_dma_memory_zone(p) {
			z = container_of(p, struct memory_zone, n_zone_type_list);
			if (z->allocator) {
				mem = z->allocator->memalign(z, alignment, bytes);
				if (mem) {
					sort_zone(z);
					return mem;			
				}			
			}
		}
	} else if (maf_is_sram(flags)) {
		for_each_sram_memory_zone(p) {
			z = container_of(p, struct memory_zone, n_zone_type_list);
			if (z->allocator) {
				mem = z->allocator->memalign(z, alignment, bytes);
				if (mem) {
					sort_zone(z);
					return mem;			
				}			
			}
		}
	}

	for_each_sdram_memory_zone(p) {
		z = container_of(p, struct memory_zone, n_zone_type_list);
		if (z->allocator) {
			mem = z->allocator->memalign(z, alignment, bytes);
			if (mem) {
				sort_zone(z);
				return mem;			
			}			
		}
	}

	return ERR_PTR(-ENOMEM);
}


size_t kmalloc_footprint(void)
{
	struct list_head *p;
	struct memory_zone *z;
	unsigned int sum = 0;
	
	for_each_memory_zone(p) {
		z = container_of(p, struct memory_zone, n_zone_list);
		sum += z->footprint;
	}
	return sum;
}
 
size_t kmalloc_max_footprint(void)
{
	struct list_head *p;
	struct memory_zone *z;
	unsigned int sum = 0;
	
	for_each_memory_zone(p) {
		z = container_of(p, struct memory_zone, n_zone_list);
		if (z->allocator)
			sum += z->allocator->max_footprint(z);
	}
	return sum;
}

size_t kmalloc_footprint_limit(void)
{
	struct list_head *p;
	struct memory_zone *z;
	unsigned int sum = 0;
	
	for_each_memory_zone(p) {
		z = container_of(p, struct memory_zone, n_zone_list);
		if (z->allocator)
			sum += z->allocator->footprint_limit(z);
	}
	return sum;
}

